package com.yeziji.security.component;

import cn.hutool.core.util.URLUtil;
import com.yeziji.common.business.system.constant.enums.SystemRoleTypeEnum;
import com.yeziji.common.business.system.service.SystemPermissionService;
import com.yeziji.common.context.OnlineContext;
import com.yeziji.security.common.IUserDetails;
import com.yeziji.security.service.DynamicSecurityAuth;
import com.yeziji.utils.ServletUtils;
import com.yeziji.utils.expansion.Lists2;
import com.yeziji.utils.expansion.Opt2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 动态权限地址的处理
 *
 * <p>支持可以动态加入白名单</p>
 *
 * @author hwy
 * @since 2023/11/12 21:49
 **/
public class DynamicSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
    @Autowired(required = false)
    private DynamicSecurityAuth dynamicSecurityAuth;
    @Autowired
    private SystemPermissionService systemPermissionService;

    @Override
    public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
        // 允许不设置任何白名单
        if (dynamicSecurityAuth == null) {
            return Collections.emptyList();
        }

        // 1. 存在白名单中
        String url = ((FilterInvocation) object).getRequest().getRequestURI();
        if (dynamicSecurityAuth.getIgnoreUrls().stream()
                .anyMatch(ignoreUrl -> ServletUtils.urlContains(ignoreUrl, url))) {
            return Collections.emptyList();
        }

        // 2. 如果存在 session 就直接从 session 中获取当前用户的角色权限
        final String path = URLUtil.getPath(url);
        if (OnlineContext.isHasSession()) {
            IUserDetails userDetails = (IUserDetails) OnlineContext.getOnlineUserOnlineInfo();
            Collection<ConfigAttribute> sessionSecurities = userDetails.getAuthorities().stream()
                    .filter(authority -> {
                        Collection<String> releasesUrls = authority.getReleasesUrls();
                        for (String releasesUrl : releasesUrls) {
                            if (ServletUtils.urlContains(releasesUrl, path)) {
                                return true;
                            }
                        }
                        return false;
                    })
                    .map(authority -> new SecurityConfig(authority.getAuthority()))
                    .collect(Collectors.toList());
            // 过滤出访问地址就直接返回当前 session 权限; 反之, 如果过滤不出就要判断当前路径是否需要权限(登录情况下)
            if (Lists2.isNotEmpty(sessionSecurities)) {
                return sessionSecurities;
            } else {
                // 查询全部地址格式的权限
                Set<String> permissionPaths = systemPermissionService.listAsPathOfParams();
                for (String permissionPath : permissionPaths) {
                    if (ServletUtils.urlContains(permissionPath, path)) {
                        // 如果当前访问地址需要特定权限访问那么就禁止访问
                        return getOfProhibitedAttributes();
                    }
                }
                // 当前访问路径不包含在所有权限路径中, 那么就说明不需要权限, 直接返回空集合
                return Collections.emptyList();
            }
        }

        // 3. 没登录且未进白名单, 直接禁止访问
        return getOfProhibitedAttributes();
    }

    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        if (dynamicSecurityAuth == null) {
            return Collections.emptyList();
        }

        return Opt2.nullElse(dynamicSecurityAuth.getPathRoleMap(), new HashMap<String, List<ConfigAttribute>>()).values()
                .stream()
                .flatMap(Collection::stream)
                .collect(Collectors.toList());
    }

    @Override
    public boolean supports(Class<?> clazz) {
        return true;
    }

    /**
     * 可以通过虚拟禁止的角色限制接口权限
     */
    private static Collection<ConfigAttribute> getOfProhibitedAttributes() {
        return List.of(new SecurityConfig(SystemRoleTypeEnum.PROHIBITED.getDesc()));
    }
}
